package dpreview;

import java.net.*;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;

public class CameraSearchXML {

    String reExp = "";
    Pattern reModelo;
    final String rootLink = "http://www.dpreview.com";
    final String specsLink = "http://www.dpreview.com/reviews/specs/";
    final String brandListLink =  "http://www.dpreview.com/reviews/specs.asp";

    Hashtable<String, Camera> cameras = new Hashtable<String, Camera>();
    Vector<String> brandsNames = new Vector<String>();

    CameraToXML toXML;
    public static void main(String[] args) {
        new CameraSearchXML().init();
    }

    void init() {

        // Query String
        String modelLink="";

        // adapta a jvn as definicoes locais dos EUA
        Locale.setDefault(Locale.ENGLISH);

        String query_fixed="";
        do {
            System.out.println("Enter brand name: ");
            String query = readLn(30).trim().replace(' ', '_');
	    if (query.length()>0) {
		query_fixed = getCorrectBrandName(query.toLowerCase());

		reExp = "/reviews/specs/" + query_fixed + "/[a-zA-Z0-9_-]+\\.asp";
		reModelo = Pattern.compile(reExp);

		modelLink = readHtml(specsLink + query);
	    }
        } while (modelLink.compareTo("") == 0);

        toXML = new CameraToXML(query_fixed);
	System.out.println("Getting models information...");
        getAllModelLinks(modelLink);
        getSpecs();
	System.out.println("Writing XML...");
        try {
            //gera XML
            toXML.generateXML();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
	System.out.println("Done!");
    }

    /**
     * acede a pagina de cada um dos modelos de determinada marca e
     * inicia o processo de screen scraping
     */
    void getSpecs(){
        Enumeration<String> e = cameras.keys();
        while(e.hasMoreElements()){
	    String tempKey = e.nextElement();
	    parseSpecs( readHtml(tempKey), tempKey );
        }
    }

    /**
     * recebe uma pagina e extrai os dados necessarios recorrendo
     * a expressoes regulares
     *
     * @param page
     * @param link
     */
    void parseSpecs(String page, String link){
	
	Pattern pattern;
	Matcher m;
	Camera camera = cameras.get(link);

	// NOME
	pattern = Pattern.compile("tdlabelproduct\">(.*?)</td>");
	m = pattern.matcher(page);
	if(m.find()){
	    camera.setModel(m.group(1));
	    System.out.println(m.group(1));
	}
	
        // LINK REVIEW
        if(m.find()){
	    pattern = Pattern.compile("href=\"(.*?)\"");
	    m = pattern.matcher(m.group(1));
	    if(m.find())
		camera.setReview_link(rootLink + m.group(1));
        }

        // DESCRICAO
        pattern = Pattern.compile("<div class=\"description\">(\\s*?)<p>(.*?<p>)*(&nbsp;)*(.*?)</p>");
        m = pattern.matcher(page);
        if(m.find())
	    camera.setDescription(m.group(4));

        // DATA PUBLICACAO
        pattern = Pattern.compile("Announced\\s*([0-9]+-[a-zA-Z]+-[0-9]+)");
        m = pattern.matcher(page);
        if(m.find()){
	    try {
		    DateFormat df = new SimpleDateFormat("dd-MMM-yy");
		    //System.out.println("> "+m.group(1));
		    camera.setAnounced(df.parse(m.group(1)));
	    } catch (ParseException e) {
		    e.printStackTrace();
	    }
        }

        // RESOLUCAO MAXIMA
        pattern = Pattern.compile("Max resolution(.*?)<td class=\"tdcontentsm\">(.*?)</td>");
        m = pattern.matcher(page);
        if(m.find()){
	    String[] res = m.group(2).split("(\\s)?x(\\s)?");
	    camera.setMax_res(Integer.parseInt(res[0]), Integer.parseInt(res[1]));
        }

        // RESOLUCOES MAIS BAIXAS
        //System.out.println("----------------------------- "+camera.getModel());
        pattern = Pattern.compile("Low resolution(.*?)<td class=\"tdcontentsm\">(<span.*?>)?(.*?)</td>");
        m = pattern.matcher(page);
        if(m.find()){
	    String resolutions = m.group(3);
	    pattern = Pattern.compile("([0-9]+)\\s*x\\s*([0-9]+)+");
	    m = pattern.matcher(resolutions);
	    int w, h;
	    while(m.find()){
		w = Integer.parseInt(m.group(1).trim());
		//m.find();
		h = Integer.parseInt(m.group(2).trim());
		//System.out.println(w + " x " + h);
		camera.addLowResolution(w, h);
	    }
        }

	// IMAGE RATIO
	pattern = Pattern.compile("Image ratio(.*?)<td class=\"tdcontentsm\">(<span.*?>)?(.*?)</td>");
        m = pattern.matcher(page);
	Vector<int[]> aux_vect = new Vector<int[]>();
        if(m.find()) {
	    String resolutions = m.group(3);
	    pattern = Pattern.compile("([0-9]+)\\s*:\\s*([0-9]+)+");
	    m = pattern.matcher(resolutions);
	    int w, h;
	    while(m.find()){
		w = Integer.parseInt(m.group(1).trim());
		//m.find();
		h = Integer.parseInt(m.group(2).trim());
		//System.out.println(w + " : " + h);
		int[] aux_array = new int[2];
		aux_array[0] = w;
		aux_array[1] = h;
		//camera.addLowResolution(w,h);
		aux_vect.add(aux_array);
	    }
	    camera.setImage_ratio(aux_vect);
	}
	
        // EFFECTIVE PIXELS
        pattern = Pattern.compile("Effective pixels(.*?)<td class=\"tdcontentsm\">(.*?) million</td>");
        m = pattern.matcher(page);
        if(m.find())
	    camera.setEfective_pixels(Float.parseFloat(m.group(2)));


        // SENSOR SIZE
        pattern = Pattern.compile("Sensor size(.*?)<td class=\"tdcontentsm\">(.*?)</td>");
        m = pattern.matcher(page);
        if(m.find()){
                String sensor_size = m.group(2);

                sensor_size = sensor_size.replaceAll("<span(.*?)>", "");
                sensor_size = sensor_size.replaceAll("</span>", "");
                if(sensor_size.contains("\"")){
		    String sensor_size_fraction = sensor_size.substring( sensor_size.indexOf("/") - 1, sensor_size.indexOf("\"")).trim();
		    camera.setSensor_size_fraction(sensor_size_fraction);
                }

                if(sensor_size.contains("mm")){
		    pattern = Pattern.compile("([0-9]+(\\.[0-9]+)?)\\s*x\\s*([0-9]+(\\.[0-9]+)?)");
		    m = pattern.matcher(sensor_size);
		    m.find();

		    camera.setSensor_size_width(m.group(1));
		    camera.setSensor_size_height( m.group(3));
                }

                if(sensor_size.contains("cm")){
		    pattern = Pattern.compile("([0-9]+(\\.[0-9]+)?)\\s*cm");
		    m = pattern.matcher(sensor_size);
		    m.find();
		    camera.setSensor_size_area(m.group(1).trim());
                }
        }

        // ISO RATING
	pattern = Pattern.compile("ISO rating(.*?)<td class=\"tdcontentsm\">(<span.*?>Unknown)?(.*?)(</span>)?</td>");
	m = pattern.matcher(page);
	if(m.find()){
	    StringTokenizer tokens = new StringTokenizer(m.group(3),",");
	    Vector<String> aux = new Vector<String>();
	    while(tokens.hasMoreTokens()) {
		aux.add(tokens.nextToken());
	    }
	    camera.setISO_rating(aux);
	}

        // MIN SHUTTER SPEED
	String minShutterSpeed = "", maxShutterSpeed = "";
        pattern = Pattern.compile("Min shutter(.*?)<td class=\"tdcontentsm\">(&nbsp;)*(.*?)</td>");
        m = pattern.matcher(page);
        if(m.find())
	    minShutterSpeed = m.group(3).replaceFirst("(\\s)sec", "").trim();
        

        // MAX SHUTTER SPEED
	pattern = Pattern.compile("Max shutter(.*?)<td class=\"tdcontentsm\">(&nbsp;)*(.*?)</td>");
	m = pattern.matcher(page);
	if(m.find())
		maxShutterSpeed = m.group(3).replaceFirst("(\\s)sec", "").trim();

	if(!minShutterSpeed.equals("") && !maxShutterSpeed.equals(""))
	    camera.setShutter(new String[]{minShutterSpeed, maxShutterSpeed});
	else
	    camera.setShutter(null);

	// LINK IMAGEM
	pattern = Pattern.compile("http://a.img-dpreview.com/reviews/images/[a-zA-Z0-9_-]+\\.gif");
	m = pattern.matcher(page);
	if(m.find())
	    camera.setImage_link(m.group(0));

	// No fim do parse adiciona-se à lista de cameras para guardar em xml
	toXML.addCamera(camera);
    }

    /**
     * recolhe as marcas presentes na base de dados
     *
     * @param query_brand parametro referente a marca a pesquisar
     * @return
     */
    String getCorrectBrandName(String query_brand) {
        loadAvailableBrands();
        for (String curr_brand : brandsNames) {
	    curr_brand = curr_brand.replace(' ','_');
		if(curr_brand.compareToIgnoreCase(query_brand) == 0)
		    return curr_brand;
	}
        return "";
    }

    /**
     * carrega todas as marcas presentes na base de dados
     */
    void loadAvailableBrands() {
        String reg_brands = "alt=\"([^\"]+)\"";
        Pattern res_brands = Pattern.compile(reg_brands);

        brandsNames = new Vector<String>();
        brandsNames = getBrands(readHtml(brandListLink), res_brands);
    }

    /**
     * mostra ao utilizador as marcas disponiveis na base de dados
     */
    void printAvailableBrands() {
        System.out.println("Brands Available:");
        for (String curr_brand : brandsNames) {
        	 System.out.print(curr_brand + ", ");
		}
        System.out.println();
    }

    /**
     * extrai todas as marcas presentes na pagina
     * page baseando-se na expressao regular p
     *
     * @param page pagina HTML a ser analisada
     * @param p expressao regular a ser aplicada a pagina
     * @return
     */
    Vector<String> getBrands(String page, Pattern p) {
        Matcher m = p.matcher(page);
        Vector<String> aux = new Vector<String>();
        while (m.find())
            aux.add(m.group(1));
        return aux;
    }

    /**
     * faz HTTP GET ao url dado e devolve a pagina numa string
     *
     * @param url link da pagina a ser pedida
     * @return
     */
    String readHtml(String url){
	URL dpreview;
	String pagina = "";

	try {
		dpreview = new URL(url);
		URLConnection connection = dpreview.openConnection();
		connection.setRequestProperty("User-Agent", "");
	    BufferedReader br =
	      new BufferedReader(new InputStreamReader(connection.getInputStream()));

	    String line = null;
	    do
	    {
	      line = br.readLine();
	      if (line != null){
		  pagina += line;
	      }
	    } while (line != null);

	    br.close();
	} catch (MalformedURLException e) {
	    e.printStackTrace();
	} catch (UnsupportedEncodingException e) {
	    e.printStackTrace();
	} catch (IOException e) {
	    printAvailableBrands();
	    pagina="";
	}
	return pagina;
    }

    /**
     * extrai os links referentes as especificacoes
     * de cada modelo da marca a pasquisar
     *
     * @param page String com o HTML de uma determinada marca
     */
    void getAllModelLinks(String page) {
        Matcher m = reModelo.matcher(page);

        while (m.find()) {
            String temp = m.group(0);
            String link = rootLink + temp;
            if (!cameras.containsKey(link)) {
                cameras.put(link, new Camera(link));
                //System.out.println(link);
            }
        }
    }

    /**
     * leitura de caracteres a partir da consola
     *
     * @param maxLg tamanho maximo da string a ser lida
     * @return
     */
    String readLn(int maxLg) {
        byte lin[] = new byte[maxLg];
        int lg = 0, car = -1;
        try {
            while (lg < maxLg) {
                car = System.in.read();
                if ((car < 0) || (car == '\n')) {
                    break;
                }
                lin[lg++] += car;
            }
        } catch (IOException e) {
            return (null);
        }
        if ((car < 0) && (lg == 0)) {
            return (null);
        }
        return (new String(lin, 0, lg));
    }
}
